package vm

import (
	"math/rand"
	"kumachan/standalone/rx"
	"kumachan/interpreter/runtime/api"
	"kumachan/interpreter/runtime/lib/librpc"
	. "kumachan/interpreter/runtime/rt"
)


func Execute(p *Program, opts *Options) {
	tryDumpAsm(opts.AsmDumpPath, p)
	var sched = rx.TrivialScheduler {
		EventLoop: rx.SpawnEventLoop(),
	}
	var pool = CreateGoroutinePool()
	var m = &Machine {
		program:   p,
		options:   opts,
		pool:      pool,
		scheduler: sched,
		worker1:   rx.CreateWorker(),
		worker2:   rx.CreateWorker(),
	}
	generateObjects(m)
	evaluateThunks(m)
	runAllEffects(m)
}

type GeneratedObjects struct {
	functions  [] FuncValue
	funcMap    map[Symbol] uint
	funcCtx    *FunctionSeedEvaluator
	dispatch   DispatchTableMapping
	vdApi      VdApi
	rpcApi     RpcApi
}
func (g *GeneratedObjects) MustGetFuncValue(sym Symbol) FuncValue {
	var index, exists = g.funcMap[sym]
	if !(exists) { panic("func not found: " + sym.String()) }
	return g.functions[index]
}
func generateObjects(m *Machine) {
	var g = &(m.GeneratedObjects)
	{ var program_functions = m.program.Functions
	var functions = make([] FuncValue, len(program_functions))
	var funcMap = make(map[Symbol] uint)
	for i, seed := range program_functions {
		var index = uint(i)
		var info = seed.GetInfo()
		funcMap[info.Symbol] = index
		functions[index] = new(ValFunc)
	}
	g.functions = functions
	g.funcMap = funcMap }
	{ var dm = m.program.DispatchDef.Evaluate(m.MustGetFuncValue)
	g.dispatch = dm }
	{ var vd_api, rpc_api = librpc.CreateVdRpcApi(m.program.RpcConf, m)
	g.vdApi = vd_api
	g.rpcApi = rpc_api }
	{ var ctx = &FunctionSeedEvaluator {
		LibraryNativeFunctionCollection: func(id string) NativeFunction {
			return api.GetNativeFunctionValue(id)
		},
		StaticValueSeedEvaluator: StaticValueSeedEvaluator {
			MustGetFuncValue: m.MustGetFuncValue,
		},
		DispatchTableMapping: m.dispatch,
	}
	for i, seed := range m.program.Functions {
		*(m.functions[i]) = *(seed.Evaluate(ctx))
	}
	var err = api.CheckUnusedNativeFunctions()
	if err != nil { panic(err) }
	g.funcCtx = ctx }
}

func evaluateThunks(m *Machine) {
	for _, index := range m.program.ThunkSeq {
		var f = m.functions[index]
		var v = m.Call(rx.Background(), f, nil)
		*f = *(ValFuncNative(func(_ Value, _ InteropContext) Value {
			return v
		}))
	}
}

func runAllEffects(m *Machine) {
	var effects = make([] rx.Observable, 0)
	for _, index := range m.program.Effects {
		var f = m.functions[index]
		var v = m.Call(rx.Background(), f, nil)
		var e = v.(rx.Observable)
		effects = append(effects, e)
	}
	for _, e := range m.program.Attachments.InjectedEffects {
		effects = append(effects, e(m))
	}
	rand.Shuffle(len(effects), func(i, j int) {
		var t = effects[i]
		effects[i] = effects[j]
		effects[j] = t
	})
	var wg = make(chan bool, len(effects))
	for _, e := range effects {
		rx.Schedule(e, m.scheduler, rx.Receiver {
			Context:   rx.Background(),
			Terminate: wg,
		})
	}
	for range effects {
		<- wg
	}
}


